package com.zeyu.framework.core.configuration;

import com.google.common.collect.Sets;
import com.zeyu.framework.core.common.Constant;
import com.zeyu.framework.tools.schedule.AutowiringSpringBeanJobFactory;
import com.zeyu.framework.tools.schedule.annotation.AutoConfigurationJob;
import com.zeyu.framework.tools.schedule.entity.JobEntity;
import com.zeyu.framework.tools.schedule.service.JobService;
import com.zeyu.framework.utils.DateUtils;
import com.zeyu.framework.utils.SpringContextHolder;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.quartz.Job;
import org.quartz.impl.jdbcjobstore.JobStoreTX;
import org.quartz.impl.jdbcjobstore.StdJDBCDelegate;
import org.quartz.simpl.SimpleThreadPool;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.BeanDefinitionHolder;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.GenericBeanDefinition;
import org.springframework.cglib.core.SpringNamingPolicy;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ApplicationListener;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ClassPathBeanDefinitionScanner;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.core.env.Environment;
import org.springframework.core.type.filter.AnnotationTypeFilter;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.quartz.SchedulerFactoryBean;
import org.springframework.stereotype.Component;
import org.springframework.transaction.PlatformTransactionManager;

import javax.annotation.PostConstruct;
import javax.sql.DataSource;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Date;
import java.util.Properties;
import java.util.Set;
import java.util.concurrent.TimeUnit;

/**
 * quartz自动配置类,因为我们需要数据库配置任务,所以自动生成detail、trigger的bean不注入
 * basic执行方式3种:
 * 1.每隔n秒 @Scheduled(fixedRate = 5000)
 * 2.延迟n秒 @Scheduled(fixedDelay = 5000)
 * 3.表达式  @Scheduled(cron = "*\/5 * * * * *")
 * Note:
 * 异步 @Async 使用注意:
 * 1.static方法不能异步
 * 2.内部调用不能异步
 * 3.重复扫描不能异步
 * Created by zeyuphoenix on 16/6/20.
 */
@Configuration
@EnableScheduling         // 启用定时任务,默认为basic,使用在方法上加入注解方式实现
@EnableAsync              // 开启异步调用管理
public class QuartzConfiguration implements Constant {

    // ================================================================
    // Constants
    // ================================================================

    /**
     * logger
     */
    private static final Logger logger = LoggerFactory.getLogger(QuartzConfiguration.class);

    // ================================================================
    // Fields
    // ================================================================

    @Autowired
    private DataSource dataSource;

    @Autowired
    private PlatformTransactionManager transactionManager;

    @Autowired
    private ApplicationContext applicationContext;

    /**
     * 保存所有基于注解的job
     */
    private static Set<String> annotationSet = Sets.newCopyOnWriteArraySet();

    // ================================================================
    // Constructors
    // ================================================================

    // ================================================================
    // Methods from/for super Interfaces or SuperClass
    // ================================================================

    // ================================================================
    // Public or Protected Methods
    // ================================================================

    @PostConstruct
    public void init() {

        logger.info("QuartzConfig initialized.");
    }

    // JobDetailFactoryBean
    // CronTriggerFactoryBean

    /**
     * quartz持久化对象构建
     */
    @Bean(name = "quartzScheduler")
    public SchedulerFactoryBean quartzScheduler() {
        SchedulerFactoryBean quartzScheduler = new SchedulerFactoryBean();

        quartzScheduler.setDataSource(dataSource);
        quartzScheduler.setTransactionManager(transactionManager);
        // this allows to update triggers in DB when updating settings in config file:
        // QuartzScheduler 启动时更新己存在的Job，这样就不用每次修改targetObject后删除qrtz_job_details表对应记录了
        quartzScheduler.setOverwriteExistingJobs(true);
        quartzScheduler.setSchedulerName("quartz-scheduler");
        quartzScheduler.setAutoStartup(true);
        quartzScheduler.setApplicationContextSchedulerContextKey("applicationContextKey");

        // custom job factory of spring with DI support for @Autowired!
        AutowiringSpringBeanJobFactory jobFactory = new AutowiringSpringBeanJobFactory();
        jobFactory.setApplicationContext(applicationContext);
        quartzScheduler.setJobFactory(jobFactory);
        //QuartzScheduler 延时启动，应用启动完20秒后 QuartzScheduler 再启动
        quartzScheduler.setStartupDelay(20);

        quartzScheduler.setQuartzProperties(quartzProperties());

        return quartzScheduler;
    }

    /**
     * 加载quartz数据配置
     */
    @Bean
    public Properties quartzProperties() {

        // 配置
        Properties properties = new Properties();

        /* PropertiesFactoryBean propertiesFactoryBean = new PropertiesFactoryBean();
        propertiesFactoryBean.setLocation(new ClassPathResource("/quartz.properties"));

        try {
            propertiesFactoryBean.afterPropertiesSet();
            properties = propertiesFactoryBean.getObject();

        } catch (IOException e) {
            logger.warn("Cannot load quartz.properties.", e);
        }*/

        // quartz.properties 的配置基本都是固定配置,这里省去配置文件,直接使用代码配置
        // Default Properties file for use by StdSchedulerFactory to create a Quartz Scheduler Instance
        properties.put("org.quartz.scheduler.instanceName", "DefaultQuartzScheduler");
        // Configure ThreadPool
        properties.put("org.quartz.threadPool.class", SimpleThreadPool.class.getName());
        properties.put("org.quartz.threadPool.threadCount", "5");
        properties.put("org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread", "true");
        // Configure JobStore
        properties.put("org.quartz.jobStore.class", JobStoreTX.class.getName());
        properties.put("org.quartz.jobStore.driverDelegateClass", StdJDBCDelegate.class.getName());
        properties.put("org.quartz.jobStore.useProperties", "true");
        // 默认表前缀
        String prefix = this.applicationContext.getEnvironment().getProperty(CONFIGURATION_DEFINED_PREFIX + "quartz-table-prefix", "QRTZ_");
        properties.put("org.quartz.jobStore.tablePrefix", prefix);


        return properties;
    }

    // ================================================================
    // Getter & Setter
    // ================================================================

    // ================================================================
    // Private Methods
    // ================================================================

    // ================================================================
    // Inner or Anonymous Class
    // ================================================================

    /**
     * 因为自动扫描的注解需要使用quartz注入的bean,我们需要在spring boot初始化完成后才能使用
     */
    public static class AutoConfigurationQuartzListener implements ApplicationListener<ContextRefreshedEvent> {

        // ================================================================
        // Methods from/for super Interfaces or SuperClass
        // ================================================================

        @Override
        public void onApplicationEvent(ContextRefreshedEvent event) {

            // add auto configuration annotation
            if (CollectionUtils.isNotEmpty(annotationSet)) {
                for (String jobClass : annotationSet) {
                    try {
                        autoConfigurationQuartzJob(jobClass);
                    } catch (ClassNotFoundException e) {
                        logger.error(String.format("the annotation job %s init is not find.", jobClass), e);
                    } catch (Exception e) {
                        logger.error(String.format("the annotation job %s init is error.", jobClass), e);
                    }
                }
            }
        }

        // ================================================================
        // Private Methods
        // ================================================================

        /**
         * 自动配置
         */
        private static void autoConfigurationQuartzJob(String jobClass) throws ClassNotFoundException {
            logger.info("job is auto configuration: {}", jobClass);
            // get all fields
            AutoConfigurationJob job = Class.forName(jobClass).getAnnotation(AutoConfigurationJob.class);
            if (job != null) {
                JobEntity jobEntity = buildJobEntity(job, jobClass);
                logger.info("JobEntity is {}", jobEntity);

                JobService jobService = SpringContextHolder.getBean(JobService.class);
                jobService.addJob(jobEntity);
            }
        }

        /**
         * 根据job注解生成job entity
         */
        @SuppressWarnings("unchecked")
        private static JobEntity buildJobEntity(AutoConfigurationJob job, String jobClass) throws ClassNotFoundException {
            JobEntity jobEntity = new JobEntity();
            // 设置job entity项
            jobEntity.setName(job.name());
            jobEntity.setGroup(job.group());
            jobEntity.setJobClass((Class<? extends Job>) Class.forName(jobClass));
            jobEntity.setType(job.type());
            jobEntity.setDescription(job.description());
            // cron和unit只能使用一种
            if (StringUtils.isNotEmpty(job.cron())) {
                jobEntity.setCron(job.cron());
                jobEntity.setInterval(-1);
                jobEntity.setUnit(TimeUnit.SECONDS);
            } else {
                jobEntity.setCron(null);
                jobEntity.setUnit(job.unit());
                jobEntity.setInterval(job.interval());
            }
            jobEntity.setPersistJobDataAfterExecution(job.persistJobDataAfterExecution());
            // 时间格式化设置
            if (StringUtils.isNotEmpty(job.startDate())) {
                jobEntity.setStartDate(DateUtils.parseDate(job.startDate()));
            } else {
                jobEntity.setStartDate(new Date());
            }
            if (StringUtils.isNotEmpty(job.startDate())) {
                jobEntity.setEndDate(DateUtils.parseDate(job.endDate()));
            } else {
                jobEntity.setEndDate(null);
            }

            return jobEntity;
        }
    }

    /**
     * 嵌入Spring的加载过程,Spring会在BeanFactory的相关处理完成后调用postProcessBeanFactory方法，进行定制的功能
     */
    @Component
    public static class BeanScannerConfigurer implements BeanFactoryPostProcessor, ApplicationContextAware {

        // ================================================================
        // Fields
        // ================================================================

        private ApplicationContext applicationContext;

        // ================================================================
        // Methods from/for super Interfaces or SuperClass
        // ================================================================

        @Override
        public void setApplicationContext(ApplicationContext applicationContext) {
            this.applicationContext = applicationContext;
        }

        @Override
        public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
            Scanner scanner = new Scanner((BeanDefinitionRegistry) beanFactory);
            scanner.setResourceLoader(this.applicationContext);
            Environment environment = this.applicationContext.getEnvironment();
            scanner.scan(environment.getProperty(CONFIGURATION_DEFINED_PREFIX + "quartz-scan-package"));
        }
    }

    /**
     * 定义扫描器
     */
    private static final class Scanner extends ClassPathBeanDefinitionScanner {

        // ================================================================
        // Constructors
        // ================================================================

        public Scanner(BeanDefinitionRegistry registry) {
            super(registry);
        }

        // ================================================================
        // Methods from/for super Interfaces or SuperClass
        // ================================================================

        @Override
        public void registerDefaultFilters() {
            this.addIncludeFilter(new AnnotationTypeFilter(AutoConfigurationJob.class));
        }

        @Override
        public Set<BeanDefinitionHolder> doScan(String... basePackages) {
            Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);
            for (BeanDefinitionHolder holder : beanDefinitions) {
                GenericBeanDefinition definition = (GenericBeanDefinition) holder.getBeanDefinition();
                // not need auto create bean
                // definition.getPropertyValues().add("innerClassName", definition.getBeanClassName());
                // definition.setBeanClass(FactoryBeanCreate.class);
                annotationSet.add(definition.getBeanClassName());
            }
            return beanDefinitions;
        }

        @Override
        public boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
            return super.isCandidateComponent(beanDefinition) && beanDefinition.getMetadata()
                    .hasAnnotation(AutoConfigurationJob.class.getName());
        }
    }

    /**
     * 自动创建job entity,获取类操作,暂时不需要(保留)
     */
    private static class FactoryBeanCreate<T> implements InitializingBean, FactoryBean<T> {

        // ================================================================
        // Fields
        // ================================================================

        // 注解类
        private String innerClassName;

        // ================================================================
        // Methods from/for super Interfaces or SuperClass
        // ================================================================

        @Override
        @SuppressWarnings("unchecked")
        public T getObject() throws Exception {

            logger.debug("create bean and return it.");

            Class innerClass = Class.forName(innerClassName);
            if (innerClass.isInterface()) {
                return (T) InterfaceProxy.newInstance(innerClass);
            } else {
                Enhancer enhancer = new Enhancer();
                enhancer.setSuperclass(innerClass);
                enhancer.setNamingPolicy(SpringNamingPolicy.INSTANCE);
                enhancer.setCallback(new MethodInterceptorImpl());
                return (T) enhancer.create();
            }
        }

        @Override
        public Class<?> getObjectType() {
            try {
                return Class.forName(innerClassName);
            } catch (ClassNotFoundException e) {
                logger.error("get object type error: ", e);
            }
            return null;
        }

        @Override
        public boolean isSingleton() {
            return true;
        }

        @Override
        public void afterPropertiesSet() throws Exception {
            logger.debug("create factory bean complete.");
        }

        // ================================================================
        // Public or Protected Methods
        // ================================================================

        public void setInnerClassName(String innerClassName) throws ClassNotFoundException {
            // logger.debug("innerClassName is {}", innerClassName)
            this.innerClassName = innerClassName;
        }
    }

    /**
     * 接口自动包装为类
     */
    private static class InterfaceProxy implements InvocationHandler {

        // ================================================================
        // Methods from/for super Interfaces or SuperClass
        // ================================================================

        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            logger.debug("ObjectProxy execute:" + method.getName());
            return method.invoke(proxy, args);
        }

        /**
         * new instance
         */
        @SuppressWarnings("unchecked")
        private static <T> T newInstance(Class<T> innerInterface) {
            ClassLoader classLoader = innerInterface.getClassLoader();
            Class[] interfaces = new Class[]{innerInterface};
            InterfaceProxy proxy = new InterfaceProxy();
            return (T) Proxy.newProxyInstance(classLoader, interfaces, proxy);
        }
    }

    /**
     * 类自动创建工具
     */
    private static class MethodInterceptorImpl implements MethodInterceptor {

        // ================================================================
        // Methods from/for super Interfaces or SuperClass
        // ================================================================

        @Override
        public Object intercept(Object o, Method method, Object[] objects,
                                MethodProxy methodProxy) throws Throwable {
            logger.debug("MethodInterceptorImpl:" + method.getName());
            return methodProxy.invokeSuper(o, objects);
        }
    }
    // ================================================================
    // Test Methods
    // ================================================================

}
